﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using System.Runtime.InteropServices;
using DeferredWaterDemo;
using Microsoft.Xna.Framework.Graphics; 
namespace Demo
{
    public struct ObjectProperty
    {
        public ObjectProperty(string Name, string Value)
        {
            this.Name = Name;
            this.Value = Value;
        }
        public string Name;
        public string Value;
    };
    
    struct EngineVertex
    {
        public Vector3 Position;
        public Vector3 Normal;
        public Vector3 Tangent;

        public Vector2 TextureCoordinate;
        public Vector2 StaticCoordinate;

        public Vector4 BoneWeights;
        public Vector4 BoneIndices;
    };

    public class Mesh {

        public delegate void OnReadProperties(List<ObjectProperty> properties);
        public delegate void OnReadLight(EnginePointlight light);
        public delegate void OnReadObject(string Name, Matrix transform, List<ObjectProperty> properties);
        public delegate void OnReadPhysics(int[] indices);

        static string ReadString(BinaryReader br)
        {
            string result = "";
            char c = (char)0;
            do
            {
                c = br.ReadChar();
                result += c;
            }
            while (c != (char)0);
            return (result.Length > 1) ? result.Remove(result.Length - 1) : "";
        }

        static void ReadVertex(BinaryReader br)
        {
            br.ReadSingle();
            br.ReadSingle();
            br.ReadSingle();

            br.ReadBytes(18 * 4);
        }
        static void ReadVertex(BinaryReader br, ref EngineVertex vertex)
        {
            vertex.Position.X = br.ReadSingle();
            vertex.Position.Y = br.ReadSingle();
            vertex.Position.Z = br.ReadSingle();

            vertex.Normal.X = br.ReadSingle();
            vertex.Normal.Y = br.ReadSingle();
            vertex.Normal.Z = br.ReadSingle();

            vertex.Tangent.X = br.ReadSingle();
            vertex.Tangent.Y = br.ReadSingle();
            vertex.Tangent.Z = br.ReadSingle();


            vertex.TextureCoordinate.X = br.ReadSingle();
            vertex.TextureCoordinate.Y = br.ReadSingle();

            vertex.StaticCoordinate.X = br.ReadSingle();
            vertex.StaticCoordinate.Y = br.ReadSingle();

            vertex.BoneWeights.X = br.ReadSingle();
            vertex.BoneWeights.Y = br.ReadSingle();
            vertex.BoneWeights.Z = br.ReadSingle();
            vertex.BoneWeights.W = br.ReadSingle();

            vertex.BoneIndices.X = br.ReadSingle();
            vertex.BoneIndices.Y = br.ReadSingle();
            vertex.BoneIndices.Z = br.ReadSingle();
            vertex.BoneIndices.W = br.ReadSingle();
        }
        class DataContext {

            public float transparency;
            public Texture color;
            public Texture opacity;
            public Texture normal;
            public Texture specular;
            public Effect shader;

            public EffectParameter World;
            //public EffectParameter View;
            //public EffectParameter Projection;
            public EffectParameter Time;

            public void Release() {

                if (color != null)
                    color.Dispose();
                if (opacity != null)
                    opacity.Dispose();
                if (normal != null)
                    normal.Dispose();
                if (specular != null)
                    specular.Dispose();
                if (shader != null)
                    shader.Dispose();

                color = null;
                opacity = null;
                normal = null;
                specular = null;
                shader = null;
            }
        }

        class EngineModelLOD
        {
            public List<EngineModelPart> parts = new List<EngineModelPart>();
            

            public void Release()
            {
                foreach (EngineModelPart n in parts)
                    n.Release();
                parts.Clear();
            }
        }

        class EngineModelPart
        {
            public VertexBuffer vertices;
            public int triangles;
            public DataContext data;
            public void Release()
            {
                data.Release();
                vertices = null;
                triangles = 0;
            }
        }
        List<EngineModelLOD> LOD;
        string mFilename;
      
        public bool Release()
        {
            if (LOD != null)
            {
                foreach (EngineModelLOD n in LOD)
                    n.Release();
                LOD.Clear();
            }
            return true;
        }

        public Mesh() {

        }

        private void ReadStaticLighting(BinaryReader reader)
        {
            string staticLighting = ReadString(reader);
        }

        private void ReadAnimation(BinaryReader reader)
        {
            //read animation
            short frames = reader.ReadInt16();
            short bones = reader.ReadInt16();

            int matrixCount = frames * bones;
            if (matrixCount == 0)
                throw new Exception("Error loading model. Invalid animaton data.");

            int matrixSize = Marshal.SizeOf(typeof(float)) * 16;
            int animationBytes = matrixCount * matrixSize;

            byte[] matrices = new byte[animationBytes];
            reader.Read(matrices, 0, animationBytes);
        }

        private void ReadPhysics(BinaryReader reader, OnReadPhysics physicsHandler)
        {
            long numindicies = reader.ReadInt32() * 3;
            for (long o = 0; o < numindicies; o += 3)
            {
                ReadVertex(reader);
                ReadVertex(reader);
                ReadVertex(reader);
            }
        }

        private void ReadPartMaterial(BinaryReader reader, out DataContext material, out bool isPhysics)
        {
            string shader = ReadString(reader);

            if (shader == "physics")
            {
                reader.ReadSingle();
                ReadString(reader);
                ReadString(reader);
                ReadString(reader);
                ReadString(reader);
                isPhysics = true;
                material = null;
            }
            else
            {

                DataContext temp = new DataContext();
                
                temp.transparency = reader.ReadSingle();
                EngineResource.LoadTexture(".\\Content\\Textures\\" + ReadString(reader), out temp.color);
                EngineResource.LoadTexture(".\\Content\\Textures\\" + ReadString(reader), out temp.normal);
                EngineResource.LoadTexture(".\\Content\\Textures\\" + ReadString(reader), out temp.specular);
                EngineResource.LoadTexture(".\\Content\\Textures\\" + ReadString(reader), out temp.opacity);

                if (EngineResource.LoadShader(".\\Content\\Shaders\\" + shader, out temp.shader))
                {
                    temp.World = temp.shader.Parameters["World"];
                    //temp.View = temp.shader.Parameters["View"];
                    //temp.Projection = temp.shader.Parameters["Projection"];
                    temp.Time = temp.shader.Parameters["Time"];
                }

                material = temp;
                isPhysics = false;
            }


        }

        private void ReadLODPart(BinaryReader reader, out EngineModelPart part)
        {
            long numindicies = reader.ReadInt32() * 3;
            EngineVertex[] verticies = new EngineVertex[numindicies];
            for (long k = 0; k < numindicies; k += 3)
            {
                ReadVertex(reader, ref verticies[k + 0]);
                ReadVertex(reader, ref verticies[k + 1]);
                ReadVertex(reader, ref verticies[k + 2]);
            }

            int structSize = Marshal.SizeOf(typeof(EngineVertex));
            part = new EngineModelPart();
            part.vertices = new VertexBuffer(Render.GraphicsDevice, structSize * (int)numindicies, BufferUsage.None);
            part.vertices.SetData<EngineVertex>(verticies);
            part.triangles = (int)numindicies / 3;
        }

        private void ReadPropertiesBatch(BinaryReader reader, out List<ObjectProperty> properties)
        {
            properties = new List<ObjectProperty>();

            short numproperties = reader.ReadInt16();
            for (short i = 0; i < numproperties; i++)
                properties.Add(new ObjectProperty(ReadString(reader), ReadString(reader)));
        }

        private void ReadLights(BinaryReader reader, OnReadLight lightHandler)
        {
            short numlights = reader.ReadInt16();
            if (lightHandler == null)
            {
                for (short i = 0; i < numlights; i++)
                {
                    ReadString(reader);
                    reader.ReadBytes(11 * 4);
                    ReadString(reader);
                }
            }
            else
            {
                for (short i = 0; i < numlights; i++)
                {
                    string type = ReadString(reader);
                    EnginePointlight light =
                        (type == "point") ? new EnginePointlight() :
                        (type == "spot") ? new EngineSpotlight() :
                        new EngineDirectlight();

                    light.Position = new Vector3(
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle());

                    Vector3 target = new Vector3(
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle());
                    light.Direction = (target - light.Position);

                    light.Diffuse = new Color(
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle());

                    light.Intensity = reader.ReadSingle() * 5f;
                    light.Shadows = reader.ReadSingle() > 0f;

                    Texture projectionTexture;
                    if (EngineResource.LoadTexture(ReadString(reader), out projectionTexture))
                    {
                        if(projectionTexture is Texture2D)
                        light.ProjectionTexture = projectionTexture as Texture2D;
                    }

                    lightHandler(light);
                }
            }
        }

        private void ReadObjects(BinaryReader reader, OnReadObject objectHandler)
        {
            short numObjects = reader.ReadInt16();
            List<ObjectProperty> properties;

            if (objectHandler == null)
            {
                for (short i = 0; i < numObjects; i++)
                {
                    ReadString(reader);
                    reader.ReadBytes(12 * 4);
                    ReadPropertiesBatch(reader, out properties);
                }
            }
            else
            {
                for (short o = 0; o < numObjects; o++)
                {
                    string name = ReadString(reader);
                    Matrix transform = new Matrix(
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    0,

                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    0,

                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    0,

                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    reader.ReadSingle(),
                    1);

                    ReadPropertiesBatch(reader, out properties);
                    objectHandler(name, transform, properties);
                }
            }
        }

        public const int GEOMETRY = 1;
        public const int TEXTURE = 2;
        public const int SHADER = 3;

        public bool Reload(int mask)
        {
            if (Core.IsNull(mFilename))
                return false;

            Release();
            return Create(mFilename, null, null, null, null);
        }

        public bool Create(string filename, OnReadProperties propertiesHandler, OnReadLight lightHandler, OnReadObject objectHandler, OnReadPhysics physicsHandler)
        {
            BinaryReader reader = null;
            try
            {
                reader = new BinaryReader(new FileStream(filename, FileMode.Open, FileAccess.Read));
                if (reader.ReadInt16() != 200)
                    throw new Exception("Error loading '" + filename + "'. Unknown version.");

                short attributes = reader.ReadInt16();
                bool hasAnimation = ((attributes & 1) >> 0) > 0;
                bool hasStaticLighting = ((attributes & 2) >> 1) > 0;

                if (hasStaticLighting)
                    ReadStaticLighting(reader);

                if (hasAnimation)
                    ReadAnimation(reader);

                short numlods = reader.ReadInt16();
                short numparts = 0;
                bool isPhysics = false;
                DataContext partContext;

                LOD = new List<EngineModelLOD>();

                for (short n = 0; n < numlods; n++)
                {
                    EngineModelLOD lod = new EngineModelLOD();
                    numparts = reader.ReadInt16();

                    for (short m = 0; m < numparts; m++)
                    {
                        ReadPartMaterial(reader, out partContext, out isPhysics);
                        
                        if (isPhysics)
                            ReadPhysics(reader, physicsHandler);
                        else
                        {
                            EngineModelPart part;
                            ReadLODPart(reader, out part);
                            part.data = partContext;
                            lod.parts.Add(part);
                        }
                    }

                    LOD.Add(lod);
                }

                //Read map props
                List<ObjectProperty> modelProperties;
                ReadPropertiesBatch(reader, out modelProperties);
                if (propertiesHandler != null)
                    propertiesHandler(modelProperties);

                //Read any lights
                ReadLights(reader, lightHandler);
                
                //Read any parameter/object data
                ReadObjects(reader, objectHandler);

                short eof = reader.ReadInt16();
                if (eof != 101)
                    throw new Exception();
                reader.Close();
                mFilename = filename;
                return true;
            }
            catch (Exception e)
            {
                if(reader != null)
                    reader.Close();
                mFilename = null;
                return false;
            }
        }

        public Texture PartTexture(int index)
        {
            return (index >= 0 && index < LOD[0].parts.Count) ?
                LOD[0].parts[index].data.color : null;
        }

        public void Draw(Matrix world, Effect shader)
        {
            if (LOD == null)
                return;

            foreach (EngineModelLOD n in LOD)
            {
                foreach (EngineModelPart m in n.parts)
                {
                    GraphicsDevice device = Render.GraphicsDevice;
                    int vertexStride = Render.GeometryVertex.GetVertexStrideSize(0);

                    if (Core.IsNull(shader))
                    {
                        Effect shade = m.data.shader;
                        if (Core.IsNull(shade))
                            continue;
                        
                        shade.Parameters["World"].SetValue(world);
                        device.Vertices[0].SetSource(m.vertices, 0, vertexStride);
                        device.Textures[1] = m.data.color;
                        device.Textures[2] = m.data.opacity;
                        device.Textures[3] = m.data.normal;

                        shade.Begin();
                        foreach (EffectPass pass in shade.CurrentTechnique.Passes)
                        {
                            pass.Begin();
                            device.DrawPrimitives(PrimitiveType.TriangleList, 0, m.triangles);
                            pass.End();
                        }
                        shade.End();
                    }
                    else
                    {
                        shader.Parameters["World"].SetValue(world);
                        shader.CommitChanges();

                        device.Vertices[0].SetSource(m.vertices, 0, vertexStride);
                        device.DrawPrimitives(PrimitiveType.TriangleList, 0, m.triangles);
                    }
                }
            }
        }
    }
}
